#include "IOStream.h"
#include <windows.h>

namespace Con
{
	namespace detail
	{
		// standard constructor for child processes
		IOStream::IOStream(const HANDLE hIns[NUM_HANDLES], const HANDLE hOuts[NUM_HANDLES]) : cFlags(NONE)
		{
			hOutputs[CHILD] = hOuts[CHILD];
			hOutputs[PARENT] = hOuts[PARENT];
			hInputs[CHILD] = hIns[CHILD];
			hInputs[PARENT] = hIns[PARENT];
		}

		// constructor for the process' built-in console
		IOStream::IOStream(int) : cFlags(REPRESENTS_DEFAULT_CONSOLE)
		{
			hInputs[CHILD] = GetStdHandle(STD_INPUT_HANDLE);
			hOutputs[PARENT] = GetStdHandle(STD_OUTPUT_HANDLE);
			if(GetFileType(hOutputs[PARENT]) != FILE_TYPE_CHAR)
			{
				cFlags = static_cast<Flags>(cFlags | REDIRECTED_STD_HANDLES);
				// these are used to write and read from standard input
				// even when the the standard handles have been redirected
				hOutputs[CHILD] = CreateFile(TEXT("CONOUT$"), GENERIC_WRITE, FILE_SHARE_WRITE, NULL, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, NULL);
			}
		}

		IOStream::IOStream(const IOStream& other) : cFlags(other.cFlags)
		{
			HANDLE hSourceProc = GetCurrentProcess();
			// test our flag
			if(!(cFlags & REPRESENTS_DEFAULT_CONSOLE))
			{
				// if this is for a child process, then duplicate all the handles
				DuplicateHandle(hSourceProc, other.hOutputs[PARENT], hSourceProc, &hOutputs[PARENT], 0, FALSE, DUPLICATE_SAME_ACCESS);
				DuplicateHandle(hSourceProc, other.hInputs[CHILD], hSourceProc, &hInputs[CHILD], 0, FALSE, DUPLICATE_SAME_ACCESS);
				DuplicateHandle(hSourceProc, other.hInputs[PARENT], hSourceProc, &hInputs[PARENT], 0, FALSE, DUPLICATE_SAME_ACCESS);
copy_redirected_handle:
				DuplicateHandle(hSourceProc, other.hOutputs[CHILD], hSourceProc, &hOutputs[CHILD], 0, FALSE, DUPLICATE_SAME_ACCESS);
			}
			else // just copy them over, since they don't belong to us
			{
				hInputs[CHILD] = other.hInputs[CHILD];
				hOutputs[PARENT] = other.hOutputs[PARENT];
				if(cFlags & REDIRECTED_STD_HANDLES)
				{
					// our handles to the console need copying
					goto copy_redirected_handle;
				}
			}
		}

		IOStream::~IOStream()
		{
			// close all handles if this wasn't for the windows provided console
			if(!(cFlags & REPRESENTS_DEFAULT_CONSOLE))
			{
				CloseHandle(hInputs[CHILD]);
				CloseHandle(hOutputs[PARENT]);
				CloseHandle(hInputs[PARENT]);
close_redirected_handle:
				CloseHandle(hOutputs[CHILD]);
			}
			else
			{
				// if the stdin/out of the windows provided console were
				// redirected, close our handle to conout
				if(cFlags & REDIRECTED_STD_HANDLES)
				{
					goto close_redirected_handle;
				}
			}				
		}

		std::streamsize IOStream::write(const char* outBuf, std::streamsize count)
		{
			DWORD numWrote = 0;
			if((cFlags & (REPRESENTS_DEFAULT_CONSOLE | REDIRECTED_STD_HANDLES)))
			{
				// if this console is the current process' and the handles have been redirected
				// then output the buf to the screen as well to the redirected handle
				WriteFile(hOutputs[CHILD], outBuf, count, &numWrote, NULL);
			}
			return (WriteFile(hOutputs[PARENT], outBuf, count, &numWrote, NULL) && numWrote) ? numWrote : -1;
		}

		std::streamsize IOStream::read(char* inBuf, std::streamsize count)
		{
			DWORD numRead = 0;
			std::streamsize retVal = (ReadFile(hInputs[PARENT], inBuf, count, &numRead, NULL) && numRead) ? numRead : -1;
			if((retVal != -1) && (cFlags & (REPRESENTS_DEFAULT_CONSOLE | REDIRECTED_STD_HANDLES)))
			{
				// if this console is the current process' and the handles have been redirected
				// and reading didn't fail, then write what was read to the screen
				WriteFile(hOutputs[CHILD], inBuf, count, &numRead, NULL);
			}
			return retVal;
		}
	} // namespace detail
} // namespace Con
